use core::any::Any;

use axerrno::LinuxError;
use axfs_ng_vfs::{NodeFlags, VfsResult};
use axhal::mem::phys_to_virt;
use pmu_rk3588::{CRU_BASE_ADDR, PMU_BASE_ADDR, RK3588Pmu, RebootLevel as DriverRebootLevel};
use sbox_proto::pmu::{self, RebootFlags, RebootLevel, RebootRequest};
use spin::Once;
use starry_vm::VmPtr;

use crate::vfs::DeviceOps;

struct PmuState {
    pmu: RK3588Pmu,
    cru_base: usize,
}

impl PmuState {
    fn new() -> Self {
        let pmu_base = phys_to_virt(PMU_BASE_ADDR.into()).as_usize();
        let cru_base = phys_to_virt(CRU_BASE_ADDR.into()).as_usize();
        Self {
            pmu: RK3588Pmu::new(pmu_base),
            cru_base,
        }
    }

    fn reboot(&self, request: &RebootRequest) -> VfsResult<()> {
        let level = Self::convert_level(request.level)?;
        let threshold = if request.flags.contains(RebootFlags::SET_THRESHOLD) {
            Some(request.threshold)
        } else {
            None
        };
        unsafe {
            self.pmu.reboot_via_cru(self.cru_base, level, threshold);
        }
        Ok(())
    }

    fn convert_level(level: RebootLevel) -> VfsResult<DriverRebootLevel> {
        match level {
            RebootLevel::First => Ok(DriverRebootLevel::First),
            RebootLevel::Second => Ok(DriverRebootLevel::Second),
        }
    }
}

static PMU_STATE: Once<PmuState> = Once::new();

fn state() -> &'static PmuState {
    PMU_STATE.call_once(PmuState::new)
}

/// Character device that exposes PMU operations to user space.
pub struct PmuDevice;

impl PmuDevice {
    pub const fn new() -> Self {
        Self
    }
}

impl Default for PmuDevice {
    fn default() -> Self {
        Self::new()
    }
}

impl DeviceOps for PmuDevice {
    fn read_at(&self, _buf: &mut [u8], _offset: u64) -> VfsResult<usize> {
        Ok(0)
    }

    fn write_at(&self, buf: &[u8], _offset: u64) -> VfsResult<usize> {
        Ok(buf.len())
    }

    fn ioctl(&self, cmd: u32, arg: usize) -> VfsResult<usize> {
        match cmd {
            pmu::IOCTL_REBOOT => {
                let request = unsafe {
                    (arg as *const RebootRequest)
                        .vm_read_uninit()?
                        .assume_init()
                };
                state().reboot(&request)?;
            }
            _ => return Err(LinuxError::ENOTTY),
        }
        Ok(0)
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn flags(&self) -> NodeFlags {
        NodeFlags::NON_CACHEABLE | NodeFlags::STREAM
    }
}
